<template>
  <div>
    <div id="captcha"></div>
  </div>
</template>

<script>
export default {
  props: {
    canvaswidth: { type: Number, default: 310 },
    canvasheight: { type: Number, default: 155 },
    canvasimage: { type: String, default: "" },
    blockx: { type: Number, default: undefined },
    blocky: { type: Number, default: undefined },
    initBlockLeft: { type: Number, default: undefined }
  },
  data() {
    return {
      l: 42,
      r: 9,
      // w:310,
      // h:155,
      PI: Math.PI
    };
  },
  computed: {
    initData() {
      let obj = {
        // ...this.$options.data(),
        l: 42,
        r: 9,
        // w:310,
        // h:155,
        PI: Math.PI,
        w: this.canvaswidth,
        h: this.canvasheight,
        hx: this.blockx,
        hy: this.blocky
      };
      return obj;
    }
  },
  watch: {
    canvasimage() {
      if (!this.el) {
        this.init({
          el: document.getElementById("captcha")
          // onSuccess:()=>{
          //   // console.log('验证成功！')
          // },
          // onFail(){
          //   // console.log('验证失败！')
          // }
        });
      } else {
        this.reset();
      }
    }
  },
  methods: {
    getRandomNumberByRange(start, end) {
      return Math.round(Math.random() * (end - start) + start);
    },
    createCanvas(width, height) {
      const canvas = this.createElement("canvas");
      canvas.width = width;
      canvas.height = height;
      return canvas;
    },
    createImg(onload) {
      const img = this.createElement("img");
      img.crossOrigin = "Anonymous";
      img.onload = onload;
      img.onerror = () => {
        img.src = this.getRandomImg();
      };
      img.src = this.getRandomImg();
      return img;
    },
    createElement(tagName, className) {
      const elment = document.createElement(tagName);
      elment.className = className;
      return elment;
    },
    addClass(tag, className) {
      tag.classList.add(className);
    },
    removeClass(tag, className) {
      tag.classList.remove(className);
    },
    getRandomImg() {
      return this.canvasimage; //'https://picsum.photos/300/150/?image=' + this.getRandomNumberByRange(0, 1084)
    },
    draw(ctx, x, y, operation) {
      let { l, r, PI } = this.initData;
      ctx.beginPath();
      ctx.moveTo(x, y);
      ctx.arc(x + l / 2, y - r + 2, r, 0.72 * PI, 2.26 * PI);
      ctx.lineTo(x + l, y);
      ctx.arc(x + l + r - 2, y + l / 2, r, 1.21 * PI, 2.78 * PI);
      ctx.lineTo(x + l, y + l);
      ctx.lineTo(x, y + l);
      ctx.arc(x + r - 2, y + l / 2, r + 0.4, 2.76 * PI, 1.24 * PI, true);
      ctx.lineTo(x, y);
      ctx.lineWidth = 2;
      ctx.fillStyle = "rgba(255, 255, 255, 0.7)";
      ctx.strokeStyle = "rgba(255, 255, 255, 0.7)";
      ctx.stroke();
      ctx[operation]();
      ctx.globalCompositeOperation = "overlay";
    },
    sum(x, y) {
      return x + y;
    },
    square(x) {
      return x * x;
    },

    init(opts) {
      let { el, onSuccess, onFail, onRefresh } = opts;
      el.style.cssText =
        "position:relative;display:inline-block;overflow:hidden;padding-right:2px;";
      // el.style.position = el.style.position || 'relative';
      // el.style.display = el.style.display || 'inline-block';
      // el.style.overflow = el.style.overflow || 'hidden';
      this.el = el;
      this.onSuccess = onSuccess;
      this.onFail = onFail;
      this.onRefresh = onRefresh;
      this.initDOM();
      this.initImg();
      this.bindEvents();
    },
    initDOM() {
      let { w, h } = this.initData;
      const canvas = this.createCanvas(w, h); // 画布
      const block = canvas.cloneNode(true); // 滑块
      const sliderContainer = this.createElement("div", "sliderContainer");
      const refreshIcon = this.createElement("div", "refreshIcon");
      const sliderMask = this.createElement("div", "sliderMask");
      const slider = this.createElement("div", "slider");
      const sliderIcon = this.createElement("span", "sliderIcon");
      const text = this.createElement("span", "sliderText");

      block.className = "block";
      text.innerHTML = "向右滑动完成拼图";

      const el = this.el;
      el.appendChild(canvas);
      el.appendChild(refreshIcon);
      el.appendChild(block);
      slider.appendChild(sliderIcon);
      sliderMask.appendChild(slider);
      sliderContainer.style.width = w + "px";
      sliderContainer.appendChild(sliderMask);
      sliderContainer.appendChild(text);
      el.appendChild(sliderContainer);
      this.canvas = canvas;
      this.block = block;
      this.sliderContainer = sliderContainer;
      this.refreshIcon = refreshIcon;
      this.slider = slider;
      this.sliderMask = sliderMask;
      this.sliderIcon = sliderIcon;
      this.text = text;
      this.canvasCtx = canvas.getContext("2d");
      this.blockCtx = block.getContext("2d");
    },
    initImg() {
      let { w, h, l, r } = this.initData;
      const L = l + r * 2 + 3;
      const img = this.createImg(() => {
        this.hdraw();
        this.canvasCtx.drawImage(img, 0, 0, w, h);
        this.blockCtx.drawImage(img, 0, 0, w, h);
        const y = this.y - r * 2 - 1;
        const ImageData = this.blockCtx.getImageData(this.x - 3, y, L, L);
        this.block.width = L;
        this.blockCtx.putImageData(ImageData, 0, y);
      });
      this.img = img;
    },
    hdraw() {
      let { w, h, l, r, hx, hy } = this.initData;
      const L = l + r * 2 + 3;
      // 随机创建滑块的位置
      this.x = hx || this.getRandomNumberByRange(L + 10, w - (L + 10));
      this.y = hy || this.getRandomNumberByRange(10 + r * 2, h - (L + 10));
      this.draw(this.canvasCtx, this.x + 3, this.y, "fill");
      this.draw(this.blockCtx, this.x, this.y, "clip");
    },
    clean() {
      let { w, h } = this.initData;
      this.canvasCtx.clearRect(0, 0, w, h);
      this.blockCtx.clearRect(0, 0, w, h);
      this.block.width = w;
    },
    bindEvents() {
      let { w, h } = this.initData;
      this.el.onselectstart = () => false;
      this.refreshIcon.onclick = () => {
        this.reset();
        typeof this.onRefresh === "function" && this.onRefresh();
      };

      let originX,
        originY,
        trail = [],
        isMouseDown = false;

      const handleDragStart = function(e) {
        originX = e.clientX || e.touches[0].clientX;
        originY = e.clientY || e.touches[0].clientY;
        isMouseDown = true;
      };

      const handleDragMove = e => {
        if (!isMouseDown) return false;
        const eventX = e.clientX || e.touches[0].clientX;
        const eventY = e.clientY || e.touches[0].clientY;
        const moveX = eventX - originX;
        const moveY = eventY - originY;
        if (moveX < 0 || moveX + 38 >= w) return false;
        this.slider.style.left = moveX + "px";
        const blockLeft = Math.ceil(((w - 40 - 20) / (w - 40)) * moveX);
        this.block.style.left = blockLeft + (this.initBlockLeft || 0) + "px";

        this.addClass(this.sliderContainer, "sliderContainer_active");
        this.sliderMask.style.width = moveX + "px";
        trail.push(moveY);
      };

      const handleDragEnd = e => {
        if (!isMouseDown) return false;
        isMouseDown = false;
        const eventX = e.clientX || e.changedTouches[0].clientX;
        if (eventX == originX) return false;
        this.removeClass(this.sliderContainer, "sliderContainer_active");
        this.trail = trail;
        const { spliced, verified, distance } = this.verify();
        if (spliced) {
          if (verified) {
            this.addClass(this.sliderContainer, "sliderContainer_success");
            typeof this.onSuccess === "function" && this.onSuccess();
            this.$emit("finish", { status: "success", distance });
          } else {
            this.addClass(this.sliderContainer, "sliderContainer_fail");
            this.text.innerHTML = "再试一次";
            this.$emit("finish", { status: "warning", distance });
            this.reset();
          }
        } else {
          this.addClass(this.sliderContainer, "sliderContainer_fail");
          typeof this.onFail === "function" && this.onFail();
          this.$emit("finish", { status: "fail", distance });
          setTimeout(() => {
            this.reset();
          }, 1000);
        }
      };
      this.slider.addEventListener("mousedown", handleDragStart);
      this.slider.addEventListener("touchstart", handleDragStart);
      document.addEventListener("mousemove", handleDragMove);
      document.addEventListener("touchmove", handleDragMove);
      document.addEventListener("mouseup", handleDragEnd);
      document.addEventListener("touchend", handleDragEnd);
    },
    verify() {
      const arr = this.trail; // 拖动时y轴的移动距离
      const average = arr.reduce(this.sum) / arr.length;
      const deviations = arr.map(x => x - average);
      const stddev = Math.sqrt(
        deviations.map(this.square).reduce(this.sum) / arr.length
      );
      const left = parseInt(this.block.style.left);
      return {
        distance: left - (this.initBlockLeft || 0),
        spliced: Math.abs(left - this.x) < 5,
        verified: stddev !== 0 // 简单验证下拖动轨迹，为零时表示Y轴上下没有波动，可能非人为操作
      };
    },
    reset() {
      this.sliderContainer.className = "sliderContainer";
      this.slider.style.left = 0;
      this.block.style.left = (this.initBlockLeft || 0) + "px";
      this.sliderMask.style.width = 0;
      this.clean();
      this.img.src = this.getRandomImg();
    }
  },
  created() {},
  mounted() {}
};
</script>

<style scoped>
/* #captcha >>> {} */
#captcha >>> .block {
  position: absolute;
  left: 0;
  top: 0;
}

#captcha >>> .sliderContainer {
  position: relative;
  text-align: center;
  /* width: 310px; */
  height: 40px;
  line-height: 40px;
  margin-top: 5px;
  background: #f7f9fa;
  color: #45494c;
  border: 1px solid #e4e7eb;
}

#captcha >>> .sliderContainer_active .slider {
  height: 38px;
  top: -1px;
  border: 1px solid #1991fa;
}

#captcha >>> .sliderContainer_active .sliderMask {
  height: 38px;
  border-width: 1px;
}

#captcha >>> .sliderContainer_success .slider {
  height: 38px;
  top: -1px;
  border: 1px solid #52ccba;
  background-color: #52ccba !important;
}

#captcha >>> .sliderContainer_success .sliderMask {
  height: 38px;
  border: 1px solid #52ccba;
  background-color: #d2f4ef;
}

#captcha >>> .sliderContainer_success .sliderIcon {
  background-position: 0 0 !important;
}

#captcha >>> .sliderContainer_fail .slider {
  height: 38px;
  top: -1px;
  border: 1px solid #f57a7a;
  background-color: #f57a7a !important;
}

#captcha >>> .sliderContainer_fail .sliderMask {
  height: 38px;
  border: 1px solid #f57a7a;
  background-color: #fce1e1;
}

#captcha >>> .sliderContainer_fail .sliderIcon {
  top: 14px;
  background-position: 0 -82px !important;
}
#captcha >>> .sliderContainer_active .sliderText,
#captcha >>> .sliderContainer_success .sliderText,
#captcha >>> .sliderContainer_fail .sliderText {
  display: none;
}

#captcha >>> .sliderMask {
  position: absolute;
  left: 0;
  top: 0;
  height: 40px;
  border: 0 solid #1991fa;
  background: #d1e9fe;
}

#captcha >>> .slider {
  position: absolute;
  top: 0;
  left: 0;
  width: 40px;
  height: 40px;
  background: #fff;
  box-shadow: 0 0 3px rgba(0, 0, 0, 0.3);
  cursor: pointer;
  transition: background 0.2s linear;
}

#captcha >>> .slider:hover {
  background: #1991fa;
}

#captcha >>> .slider:hover .sliderIcon {
  background-position: 0 -13px;
}

#captcha >>> .sliderIcon {
  position: absolute;
  top: 15px;
  left: 13px;
  width: 14px;
  height: 12px;
  background: url("../assets/icon_light.f13cff3.png") 0 -26px;
  background-size: 34px 471px;
}

#captcha >>> .refreshIcon {
  position: absolute;
  right: 0;
  top: 0;
  width: 34px;
  height: 34px;
  cursor: pointer;
  background: url("../assets/icon_light.f13cff3.png") 0 -437px;
  background-size: 34px 471px;
}
</style>
